package com.yohann.boot.config.aspect;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.yohann.boot.config.Constants;
import com.yohann.boot.util.WebUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * <p>
 * 全局环绕日志
 * </p>
 *
 * @author Yohann
 * @since 2021/1/10 19:47
 */
@Aspect
@Slf4j
@ControllerAdvice
@ConditionalOnProperty(value = "global-around-log.enable", havingValue = "true")
public class GlobalAroundLogAspect {

    @Pointcut(Constants.LOG_EXECUTION +
            "(@annotation(org.springframework.web.bind.annotation.RequestMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.GetMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.PostMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.PutMapping) ||" +
            "@annotation(org.springframework.web.bind.annotation.DeleteMapping))")
    public void pointCut() {
    }

    /**
     * 环绕通知
     */
    @Around("pointCut()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        String requestId = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
        Instant begin = Instant.now();

        //目标控制器
        Object target = pjp.getTarget();
        String className = target.getClass().getName();

        //执行接口
        String requestUrl = WebUtils.getRequest().getRequestURI();

        //执行方法
        String operation = "";
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (Objects.nonNull(apiOperation)) {
            operation = apiOperation.value();
        }
        String methodName = method.getName();

        //请求参数
        Object[] args = pjp.getArgs();
        String[] parameterNames = signature.getParameterNames();
        Map<String, Object> paramMap = buildParamMap(args, parameterNames);
        // before
        log.info("\n" +
                "________________________________________________________________________________\n" +
                ">===== 开始请求\n" +
                ">===== 请求id：{}\n" +
                ">===== 接口：{}({})，\n" +
                ">===== 执行方法：{}\n" +
                ">===== 请求参数：\n{}\n" +
                "________________________________________________________________________________", requestId, requestUrl, operation, className + "#" + methodName, JSON.toJSONString(paramMap, SerializerFeature.PrettyFormat));
        Object response;
        try {
            // action
            response = pjp.proceed();
            Instant end = Instant.now();
            // after
            log.info("\n" +
                    "________________________________________________________________________________\n" +
                    "<===== 请求结束\n" +
                    "<===== 请求id：{}\n" +
                    "<===== 接口：{}({})\n" +
                    "<===== 花费时间：{}ms\n" +
                    "<===== 响应结果：\n{}\n" +
                    "________________________________________________________________________________", requestId, requestUrl, operation, Duration.between(begin, end).toMillis(), JSON.toJSONString(response, SerializerFeature.PrettyFormat));
            return response;
        } catch (Throwable e) {
            Instant end = Instant.now();
            // throwing
            log.info("\n" +
                    "________________________________________________________________________________\n" +
                    "<===== 请求异常\n" +
                    "<===== 请求id：{}\n" +
                    "<===== 接口：{}({})\n" +
                    "<===== 花费时间：{}ms\n" +
                    "<===== 异常信息：{}\n" +
                    "________________________________________________________________________________", requestId, requestUrl, operation, Duration.between(begin, end).toMillis(), e.getMessage());
            throw e;
        }
    }

    private Map<String, Object> buildParamMap(Object[] args, String[] parameterNames) {
        Map<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof HttpServletRequest
                    || args[i] instanceof HttpServletResponse
                    || args[i] instanceof MultipartFile
                    || args[i] instanceof MultipartFile[]) {
                continue;
            }
            paramMap.put(parameterNames[i], args[i]);
        }

        return paramMap;
    }
}